/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.actions.compilation;

import com.softwarecraftsmen.orogeny.execution.SuccessOrFailure;
import com.softwarecraftsmen.orogeny.filing.AbsoluteDirectory;
import com.softwarecraftsmen.orogeny.filing.AbsoluteFile;
import com.softwarecraftsmen.orogeny.filing.AbsolutePaths;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.annotation.processing.Processor;
import javax.lang.model.SourceVersion;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Failure;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Success;
import static javax.tools.ToolProvider.getSystemJavaCompiler;

public class JavaSourceCompiler
{
	@Nullable
	private static final Iterable<String> AnnotationProcessorClassNamesOnClassPath = null;

	private static final ReentrantLock lock = new ReentrantLock(true);

	private final AbsolutePaths classPath;
	private final AbsoluteDirectory classOutputDirectory;
	private final Set<AbsoluteFile> sourceFiles;
	private final AbsoluteDirectory generatedSourceOutputDirectory;
	private final Iterable<Processor> annotationProcessors;

	public JavaSourceCompiler(final @NotNull AbsolutePaths classPath, final @NotNull AbsoluteDirectory classOutputDirectory, final @NotNull Set<AbsoluteFile> sourceFiles, final @NotNull AbsoluteDirectory generatedSourceOutputDirectory, final @NotNull Iterable<Processor> annotationProcessors)
	{
		this.classPath = classPath;
		this.classOutputDirectory = classOutputDirectory;
		this.sourceFiles = sourceFiles;
		this.generatedSourceOutputDirectory = generatedSourceOutputDirectory;
		this.annotationProcessors = annotationProcessors;
	}

	@SuppressWarnings({"ReturnInsideFinallyBlock"})
	@NotNull
	public SuccessOrFailure compile(final @NotNull DiagnosticListener<JavaFileObject> compilerErrors, final @NotNull Writer additionalCompilerOutput)
	{
		final JavaCompiler javaCompiler = getSystemJavaCompiler();
		if (javaCompiler == null)
		{
			throw new IllegalStateException("There is no System Java Compiler");
		}
		final Set<SourceVersion> sourceVersions = javaCompiler.getSourceVersions();
		final ConvenientStandardJavaFileManager javaFileManager = new ConvenientStandardJavaFileManager(javaCompiler, compilerErrors, classOutputDirectory, classPath, generatedSourceOutputDirectory);
		try
		{
			final boolean succeeded;
			final CompilationTask compilationTask = javaCompiler.getTask(additionalCompilerOutput, javaFileManager, compilerErrors, options(javaCompiler), AnnotationProcessorClassNamesOnClassPath, javaFileManager.javaSourceFiles(sourceFiles));

			compilationTask.setProcessors(annotationProcessors);


			lock.lock();
			try
			{
				succeeded = compilationTask.call();
			}
			finally
			{
				lock.unlock();
			}
			if (succeeded)
			{
				return Success;
			}
			return Failure("Compilation failed");
		}
		finally
		{
			try
			{
				javaFileManager.flush();
				javaFileManager.close();
			}
			catch (IOException e)
			{
				return Failure(e);
			}
		}
	}

	@Nullable
	private Iterable<String> options(final JavaCompiler systemJavaCompiler)
	{
		return new OptionsList(systemJavaCompiler)
		{{
			generateDebuggingInformation();
			generateDeprecationMessages();
			generateWarnings();
			disableSerialWarnings();
		}};
	}
}
